package ucd.csi.comp30220.fall2011.oteop.collision;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

import javax.jms.JMSException;
import javax.naming.NamingException;
import ucd.csi.comp30220.fall2011.oteop.flight.FlightStatus;
import ucd.csi.comp30220.fall2011.oteop.flight.FlightStatusListener;
import ucd.csi.comp30220.fall2011.oteop.flight.FlightStatusSubscriber;
import ucd.csi.comp30220.fall2011.oteop.navigation.GreatCircle;
import ucd.csi.comp30220.fall2011.oteop.navigation.Position;

/**
 * Maintain list of flights -> flight status
 * 
 * Check for flight collisions -> every combination of list pairs? Will need to
 * check if flight has landed / stopped etc
 * 
 * Maintain a collisionInfo list send details of each via jms add to list on new
 * detection remove when somebody acts on it / plane lands etc
 * 
 * @author David Haddock
 * 
 */
public class CollisionDetectionManager {

	//TODO dhaddock my listener to the flight info will have to add the flights to my list
	//TODO dhaddock this whole class
	
	private HashMap<String, FlightStatusTimer> flights;

	private HashSet<Collision> collisions;
	
	private HashMap<Collision, Integer> publishedData;
	
	private CollisionDetector collisionDetector; 
	
	private CollisionPublisher publisher;
	
	private Thread thread;
	
	private FlightStatusSubscriber flightSubscriber;
	
	public CollisionDetectionManager() throws Exception 
	{
		collisions = new HashSet<Collision>();
		publishedData = new HashMap<Collision, Integer>();
		flights = new HashMap<String, FlightStatusTimer>();
		
		collisionDetector = new CollisionDetector(1024, 21120);
		publisher = new CollisionPublisher();
		
		flightSubscriber = new FlightStatusSubscriber();
		flightSubscriber.addListener(new FlightStatusListener() {
			public void onStatusUpdate(FlightStatus status) {
				flights.put(status.getNumber(), new FlightStatusTimer(status, new Date()));
			}
		});
		
        thread = new Thread(new Runnable() {
            public void run() {
                doStuff();
            }
        });
        thread.start();
	}
	
	/**
	 * Starts the Collision Detection Module
	 * Looks for new flights, detects collisions and broadcasts the information
	 */
	private void doStuff()
	{
		//while(!thread.isInterrupted())
		while(true)
		{
			updateFlights();
			updateCollisionInfo();
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				System.out.println("The thread has failed to sleep\n"+ e.getMessage());
			}
		}
	}
	
	public boolean hasCollisionInfo(FlightStatus flight)
	{
		for (Collision col : collisions)
		{
			if (col.flights().contains(flight))
				return true;
		}
		return false;
	}
	
	public Collision getCollisionInfo(FlightStatus flight)
	{
		for (Collision col : collisions)
		{
			if (col.flights().contains(flight))
				return col;
		}
		return null;
	}
	
	/**
	 * Get a list of active flights. If a flight has landed remove it from the lsit
	 */
	private void updateFlights()
	{
	    List<FlightStatusTimer> deletes = new ArrayList<FlightStatusTimer>();
		for(FlightStatusTimer flight : flights.values())
		{
			long difference = (new Date().getTime() - flight.getDate().getTime()) / 1000;
			if (difference >= 10)
			{
				deletes.add(flight);
			}
		}
		for (FlightStatusTimer flight : deletes) {
		    flights.remove(flight.getFlightStatus().getNumber());
		}
	}
	
	/**
	 * Compare every pair of flights for a collision
	 * If there is one found, publish the information and update the collision list
	 */
	private void updateCollisionInfo() {
	    List<String> found = new ArrayList<String>();
	    for (FlightStatusTimer f1 : flights.values()) {
	        for (FlightStatusTimer f2 : flights.values()) {
				if (f1 == f2)
					continue;
				if (found.contains(f1.getFlightStatus()+"-"+f2.getFlightStatus()))
					continue;
				if (found.contains(f2.getFlightStatus()+"-"+f1.getFlightStatus()))
					continue;
				//System.err.println("Comparing: " +f1.getFlightStatus().getNumber() +" & " +f2.getFlightStatus().getNumber());
				//System.err.println("    Distance: " +GreatCircle.distanceBetween(f1.getFlightStatus().getPosition(),f2.getFlightStatus().getPosition()));
				if (GreatCircle.distanceBetween(f1.getFlightStatus().getPosition(),f2.getFlightStatus().getPosition()) > 100)
					continue;
				Collision col = collisionDetector.checkForCollison(f1.getFlightStatus(), f2.getFlightStatus());
				if (col != null) {
				    System.err.println("Publishing: " +col);
				    publishNewCollisionInfo(col);
				    found.add(f1.getFlightStatus()+"-"+f2.getFlightStatus());
				    collisions.add(col);
				}
			}
		}
	}
	
	private void publishNewCollisionInfo(Collision info) 
	{
		try {
			publisher.publish(info);
			if (publishedData.containsKey(info))
			{
				int x = publishedData.get(info).intValue();
				publishedData.remove(info);
				publishedData.put((CollisionInfo) info, new Integer(++x));
				
				if (x > 3)
				{
					//shits going down -> tell the plane
				}
			}
			else {
				publishedData.put((CollisionInfo)info, new Integer(1));
			}
			
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void setFlightStatusTimer(HashMap<String, FlightStatusTimer> map)
	{
		this.flights = map;
	}
	
	public static void main(String[] args)
	{
		try {
			CollisionDetectionManager man = new CollisionDetectionManager();
		} catch (Exception e) {
		}
	}
	
	/**
	 * Maintains a flightstatus and a datetime for comparison
	 * @author davidhaddock
	 *
	 */
	private class FlightStatusTimer {
				
		private FlightStatus status;
		private Date date;
		
		public FlightStatusTimer(FlightStatus s, Date d) {
			this.status = s;
			this.date = d;
		}
		
		public Date getDate()
		{
			return date;
		}
		
		public FlightStatus getFlightStatus()
		{
			return status;
		}
	}
}
